/*
	Class: StylesheetSelector
	
		This class represents a specific selector in a stylesheet. As such, it 
		is associated with a single Attributes object. That is, if multiple 
		entries in one or more stylesheets had the same selector description,
		they would each map to their own instance of this class.
		
		XXX Equal selectors should share their matching results though.
		
		When an instance of this class is created, it tells the Document which
		adds it to a list of "dirty" selectors. Then, the next time the Document
		renders itself, it will run through the list of dirty selectors matching
		them against the document's XML tree. Any matches will be registered
		with the matching node's Attributes instance.
		
		Also, Whenever a new DocumentElement is add to the Document, the
		StylesheetSelector registers itself as dirty with the Document. While
		this approach currently means that every selector dirties itself when an
		element is added - the idea is that it is a place to add optimizations
		that would allow a selector to decide that it is in fact not dirty.
		
		See StylesheetParser.parseSimpleSelector for the definition of the
		selector Object used in the constructor.
*/
package com.frontalcode 
{
	import flash.events.*;
	import flash.utils.*;
	
	/**                  
     * The StylesheetSelector represents a specific selector in a stylesheet.
     */
    public class StylesheetSelector extends OptionableObject
    {
		public var document : Document;
		public var attributes : Attributes;
		
		/**            
		 * @private
		 */
		internal var selector : Object;

		/**            
		 * @private
		 */
		internal var actions : Object;
		
		private var newXMLInserted : Boolean = false;
		private var lastMatchResult : Object = null;
		private var elementsById	: Object;
		private var elementsByClass	: Object;
		private var registeredDocElems : Dictionary = new Dictionary ( );
		
		static private var documentDescendants : Dictionary = new Dictionary ( true );
		
		/**
		 * Creates a new StylesheetSelector instance.
		 */
		public function StylesheetSelector ( document: Document, selector : Object, attributes : Attributes, actions : Object, options : Object = null )
		{
			super ( options );
			
			// XXX Bad option. For example the portfolio site fails.
			//
			setObjectOptionDefault ( "optimizeScrollbars", false );
			setOptionsFromLoaderParameters ( );
			
			this.document = document;
			this.selector = selector;
			this.attributes = attributes;
			this.actions = actions;
			documentDescendants [ document ] = new Dictionary ( true );
			document.registerDirtyStylesheetSelector ( this );
			addListener ( document, FrontalEvent.INSERT_XML, newDocumentXMLHandler );
			addListener ( document, FrontalEvent.ADD_ELEMENT, newDocumentElementHandler );
			document.stylesManager.registerStylesheetSelector ( this );
		}
		
		/**            
		 * @private
		 */
		static internal function onDestroyDocument ( document : Document ) : void { delete documentDescendants [ document ]; }
		
		public function get manager ( ) : StylesManager { return document.stylesManager; }
		public function get rulesetId ( ) : String { return actions == null ? null : actions.rulesetId; }
		
		/*
			Function : newDocumentXMLHandler
			
				This function handles INSERT_XML events dispatched by our
				Document. When we get one of these, we mark this selector as 
				being dirty and needing to be re-matched against the Document's
				XML tree.
				
			Parameters:
			
				event	- The INSERT_XML event.
				
			Notes:
			
				XXX It behooves us to optimize this in any way.
		*/
		/**            
		 * @private
		 */
		internal function newDocumentXMLHandler ( event : FrontalEvent ) : void
		{
			newXMLInserted = true;
			documentDescendants [ document ] = new Dictionary ( true );
			document.registerDirtyStylesheetSelector ( this );
		}
		
		/*
			Function : newDocumentElementHandler
			
				This function handles ADD_ELEMENT events dispatched by our
				Document. When we get one of these, we mark this selector as 
				being dirty but we don't mark ourselves as needing to be 
				re-matched against the Document's XML tree.
				
			Parameters:
			
				event	- The INSERT_XML event.
				
			Notes:
			
				XXX It behooves us to optimize this in any way.
		*/
		/**            
		 * @private
		 */
		internal function newDocumentElementHandler ( event : FrontalEvent ) : void
		{
			document.registerDirtyStylesheetSelector ( this );
		}
		
		/*
			Function : isSimpleSelector
			
				Simple selectors are unique because they may be evaluated 
				outside of the context of the document tree.
				
			Returns:
			
				true if this is a simple selector otherwise false.
		*/	
		/**            
		 * @private
		 */
		internal function isSimpleSelector ( ) : Boolean { return selector.selectorPieces.length == 1; }
			
		public function get name ( ) : String { return toString ( ); }
		override public function toString ( ) : String { return selectorToString ( selector ); }
		
		/**            
		 * @private
		 */
		static internal function selectorToString ( selector : Object, ignorePseudoClasses : Boolean = false ) : String
		{
			var result : String = "";
			
			for ( var i : uint = 0; i < selector.selectorPieces.length; i++ )
			{
				var simpleSelector : Object = selector.selectorPieces [ i ];
				
				if ( result.length > 0 ) result += " ";
			
				if ( simpleSelector.objectType == "SimpleSelector" )
				{
					result += simpleSelectorToString ( simpleSelector, ignorePseudoClasses );
				}
				else if ( simpleSelector.objectType == "Combinator" )
				{
					result += simpleSelector.value;
				}
			}
			
			return result;
		}
		
		/**            
		 * @private
		 */
		static internal function simpleSelectorToString ( simpleSelector : Object, ignorePseudoClasses : Boolean = false ) : String
		{
			var result : String = "";
			
			if ( ! simpleSelector.onlyNoNamespace )
			{
				result += simpleSelector.namespace == null ? "*" : simpleSelector.namespace;
			}
			
			result += "|";
			
			if ( simpleSelector.isUniversalSelector )
			{
				result += "*";
			}
			else if ( simpleSelector.elementName != null )
			{
				result += simpleSelector.elementName;
			}
			
			for each ( var constraint : Object in simpleSelector.constraints )
			{
				switch ( constraint.objectType )
				{
					case "IdSelector":
						result += "#" + constraint.name;
						break;
						
					case "ClassSelector":
						result += "." + constraint.name;
						break;
						
					case "PseudoClassSelector":
						if ( ! ignorePseudoClasses )
						{
							result += ":" + constraint.name;
							if ( constraint.value != null ) result += "(" + constraint.value + ")";
						}
						break;
						
					case "AttributeSelector":
						result += "[" + constraint.attribute;
						if ( constraint.op != null ) result += constraint.op + "\"" + constraint.value + "\""; // XXX Should escape quotes in the value.
						result += "]";
						break;
					case "NegationPseudoClass":
						result += ":not(" + simpleSelectorToString ( constraint.simpleSelector ) + ")";
				}
			}
			
			return result;
		}
		
		/*
			Function: matchElements
			
				Here we evaluate our selector against the document's XML tree. 
				The result of this process is a list of matching 
				DocumentElements that we notify about the match and any 
				constraints under which the style and attribute declarations 
				should be applied.
				
				Constraints are generated by pseudo-classes like :hover,
				:active, :link, :visited, :enabled, :selected, :disabled and
				:focus. In our implementation, a dynamic pseudo-class causes the
				selector to save the list of matching elements. Then for the
				final element chosen by the selector, it is told what elements
				it is constrained by. It can then listen to or poll those
				elements such that when all of the constraints are matched, it
				applies the StylesheetSelector's declarations.
				
				Consider this example:
				
				<style>
					container:hover:visited > container:hover .blue { alpha: .5; }
				</style>
				
				<div id="A">
					<div id="B">
						<div id="C" class="blue" />
					</div>
				</div>
				
				Here, C is listening for the mouse-over event on container B.
				(It does not listen for this event on A because it is implied by
				the event on B.) When it receives this event, it checks if
				container A has been visited. If this check is true then it
				applies the declaration setting its alpha to 0.5.
				
				The implementation of this functionality starts with what
				happens when a StylesheetSelector is being evaluated. As we
				evaluate the selector, we will store our resulsts in a "results"
				Object. This object consists of an "xmlList" property initially
				set to all the children nodes of our Document and "constraints,"
				"matchedConstraints" and "combinedNodes" properties each set to
				an empty Objects.
				
				Then as we parse each element of the selector, xmlList is
				updated to be the currently matched nodes. "constraints" is set
				to be those nodes with associated pseudoClasses that are
				ancestors of xmlList. If our selector were comprised solely of
				descendant-based combinators like " " and ">" then this result
				set would be sufficient for us to determine which
				DocumentElements should be monitored to determine when a
				selector's decalarations should be applied to the nodes in
				xmlList. But that is not the case when a combinator is not
				descendant based like the "+" (next sibling) combinator. In that
				case, we need to store the current values of xmlList and
				constraints and reset xmlList to the new set of matching nodes
				and constraints to an empty Object.
	
				We handle this last case with two Object maps:
				matchedConstraints that maps a matching XML node to a set of
				constraint nodes. And combinedNodes that maps nodes that were
				derived by non-descendant-based combinators to the matching XML
				node that the derivation was based on. These two maps allow us
				to ultimately map nodes that were derived from a series of
				combinators to a set of matching nodes and ultimately to a set
				of constraints.
				
				Then, we derive the list of DocumentElements that match the
				StylesheetSelector and register the match with it. This is so
				that when the DocumentElement reparses its attributes and
				styles, it knows which StylesheetSelectors to apply to it. (It
				also allows the DocumentElement to unapply the
				StylesheetSelector's declarations when it no longer matches or
				when the StylesheetSelector is destroyed.)
				
				At the same time we're registering the StylesheetSelector with
				each DocumentElement, we pass the list of constraints that must
				be passed before the associated declarations should be applied.
				Then the DocumentElement itself will figure out what sort of
				event listeners and such it should add to correctly implement
				these constraints. (This is done in its Attributes member.)
				
			Parameters:
			
				unmatchedXMLLists	- An array of XMLList objects of nodes newly
									  added to the document. Since they are newly
									  added we are not worried about removing old
									  matches and so do not dispatch MATCH_BEGIN
									  and MATCH_END when processing them.
				matchedSelectors	- An array of selectors that have already 
									  been matched. We'll scan these to see if
									  we can copy someone else's work.
		*/
		/**            
		 * @private
		 */
		internal function matchElements ( unmatchedXMLLists : Array, matchedSelectors : Array ) : void
		{
			document.unregisterDirtyStylesheetSelector ( this );
			
			var matchingNewElements : Boolean = isSimpleSelector ( ) && newXMLInserted && unmatchedXMLLists != null && unmatchedXMLLists.length > 0;
			
			// XXX This optimization works by assuming that there are no 
			// selectors that apply to scrollbars that can't be applied to just
			// the scrollbar XML. That is, it assumes there are no selectors
			// that partially match the scrollbar's container (or its ancestors)
			// and partially match the scrollbar itself. We support this
			// optimization because adding XML to the document means re-matching
			// selectors with combinators for the whole document.
			//
			if ( getObjectOption ( "optimizeScrollbars" ) && unmatchedXMLLists != null )
			{
				var isScrollbar : Boolean = true;
				
				for each ( var xmlList : XMLList in unmatchedXMLLists )
				{
					if ( xmlList.length ( ) != 1 || xmlList.@__frontalIsScrollbar != "true" ) 
					{
						isScrollbar = false;
						break;
					}
				}
				
				if ( isScrollbar ) matchingNewElements = true;
			}
			
			// XXX If we are not limiting our processing to the unmatched XML 
			// but there is new XML in the document tree then we throw away
			// any previous results. If we optimize this to just append to the
			// last result then we'd be doing better.
			//
			if ( ! matchingNewElements && newXMLInserted ) lastMatchResult = null;

			// XXX The "lastMatchResult != null" phrase is really just for the 
			// selector "document." Perhaps we should special case it? Or fit 
			// that node into our framework better (but avoid an extra style
			// sheet matching process)? Maybe this clause isn't really affecting
			// performance?
			//
			var xmlLists : Array = matchingNewElements && lastMatchResult != null ? unmatchedXMLLists : [ document.node ];
			
			// The result Object looks as follows during evaluation:
			//
			//	{
			//		xmlList: XMLList,
			//		constraints:
			//			[
			//				{
			//					pseudoClassName: String,
			//					pseudoClassValue: undefined | String,
			//					negate: true | false,
			//					xmlList: XMLList
			//				}
			//			],
			//		matchedConstraints:
			//			{
			//				__frontalId: 
			//					[
			//						{
			//							pseudoClassName: String,
			//							pseudoClassValue: undefined | String,
			//							negate: true | false,
			//							match: false,
			//							docElem: DocumentElement
			//						},
			//						...
			//					]
			//			},
			//		combinedNodes:
			//			{
			//				__frontalId: 
			//					{
			//						__frontalId: true,
			//						...
			//					},
			//				...
			//			}
			//	}
			//
			
			var result : Object;
			var newMatches : Boolean = true;
			
			if ( ! newXMLInserted && lastMatchResult != null )
			{
				newMatches = matchConstraints ( lastMatchResult, true, true );
				result = lastMatchResult;
			}
			else
			{
				var simpleSelectorStartIndex : uint = 0;
				var xmlListSubResults : Array = [ ];
				var constraints : Array = [ ];

				if ( ! matchingNewElements )
				{
					if  ( lastMatchResult != null )
					{
						// We'll be combining our new results with our old results.
						// Check that our old results are up-to-date.
						//
						newMatches = matchConstraints ( lastMatchResult, true, true );
					}
					else
					{
						// Check if a previously matched selector can be used as a 
						// better starting point for us to work from.
						//
						var nameWithoutPseudoClasses : String = selectorToString ( selector, true );
						var match : StylesheetSelector = null;
						var maxMatch : uint = 0;
						for ( var i : uint = 0; i < matchedSelectors.length; i++ )
						{
							var nameWithoutPseudoClasses2 : String = selectorToString ( matchedSelectors [ i ].selector, true );
							if ( nameWithoutPseudoClasses.length < nameWithoutPseudoClasses2.length )
							{
								// Not more general.
								//
								continue;
							}
							else if ( nameWithoutPseudoClasses == nameWithoutPseudoClasses2 )
							{
								// Exact match.
								//
								match = matchedSelectors [ i ];
								break;
							}
							else if ( nameWithoutPseudoClasses.substr ( 0, nameWithoutPseudoClasses2.length + 1 ) == nameWithoutPseudoClasses2 + " " && maxMatch < nameWithoutPseudoClasses2.length )
							{
								// A match.
								//
								maxMatch = nameWithoutPseudoClasses2.length;
								match = matchedSelectors [ i ];
							}
						}
						
						if ( match != null &&  match.lastMatchResult != null )
						{
							// Build up an interim search result based on this
							// match. The main complication here is that we need
							// to build our own list of constraints.
							//
							xmlLists = [ match.lastMatchResult.xmlList ];
							xmlListSubResults = match.lastMatchResult.xmlListSubResults;

							for ( i = 0; i < selector.selectorPieces.length; i++ )
							{
								var simpleSelector : Object = selector.selectorPieces [ i ];
								if ( simpleSelector.objectType == "SimpleSelector" )
								{
									for ( var j : uint = 0; j < simpleSelector.constraints.length; j++ )
									{
										if ( simpleSelector.constraints [ j ].objectType == "PseudoClassSelector" )
										{
											var selectorName : String = simpleSelector.constraints [ j ].name;
											if ( selectorName != "lang" && selectorName != "first-child" )
											{
												constraints.push ( { 
														pseudoClassName: selectorName,
														pseudoClassValue: simpleSelector.constraints [ j ].value,
														negate: simpleSelector.negate,
														simpleSelectorIndex: i
													} );
											}
										}
									}
								}
							}
							
							simpleSelectorStartIndex = match.selector.selectorPieces.length - 1;
						}
					}
				}
				
				for ( i = 0; i < xmlLists.length; i++ )
				{
					result = { 
							xmlList:			xmlLists [ i ], 
							xmlListSubResults:	xmlListSubResults,
							constraints:		constraints,
							matchedConstraints:	{ },	// Keyed by the node's __frontalId attribute.
							combinedNodes:		{ }		// Keyed by the node's __frontalId attribute.
						};
					
					// Only use our document element cache if we're actually
					// processing the document.
					//
					elementsById	= match != null || result.xmlList == document.node ? document.documentElementCacheById		: document.insertedXMLElementCacheById		[ result.xmlList ];
					elementsByClass	= match != null || result.xmlList == document.node ? document.documentElementCacheByClass	: document.insertedXMLElementCacheByClass	[ result.xmlList ];
					
					// We save full matches. If matchingNewElements is true then
					// we will append those result with lastMatchResult if it 
					// exists.
					//
					var saveResult : Boolean = result.xmlList === document.node || ( matchingNewElements && lastMatchResult == null );
					
					evaluateSelector ( result, simpleSelectorStartIndex );
					matchConstraints ( result, true, true );
					
					if ( matchingNewElements && lastMatchResult != null )
					{
						// Append our results with lastMatchResult and then use
						// the combined result.
						//
						if ( result.xmlList.length ( ) > 0 ) newMatches = true;
						appendMatchResults ( lastMatchResult, result );
						result = lastMatchResult;
					}
					else if ( saveResult ) 
					{
						lastMatchResult = result;
					}
				}
			}
			
			newXMLInserted = false;
				
			if ( ! newMatches ) return;
			
			// Now let each matching element know it's matched.
			//
			for ( var docElemKey : * in registeredDocElems )
			{
				registeredDocElems [ docElemKey ] = false;
			}
			
			for each ( var node : XML in result.xmlList )
			{
				var docElem : DocumentElement = document.getNodeElement ( node );
				
				if ( docElem == null ) continue;
				
				// Which constraints apply to this particular node?
				//
				constraints = [ ]; 
				
				for ( var combinedNodeId : String in result.combinedNodes [ node.@__frontalId ] )
				{
					if ( result.matchedConstraints [ combinedNodeId ] != null )
					{
						var constraint : Object = result.matchedConstraints [ combinedNodeId ];
						constraints = constraints.concat ( constraint );
					}
				}
				
				// We wrap the whole list of constraints into an Object
				// so that Attributes can track if the whole list is
				// matched or not. So, the list of constraints being registered
				// with the DocumentElement's Attributes looks like this:
				//
				//	{
				//		match: false,
				//		constraints:
				//			[
				//				{
				//					pseudoClassName: String,
				//					pseudoClassValue: undefined | String,
				//					negate: true | false,
				//					match: false,
				//					docElem: DocumentElement
				//				},
				//				...
				//			]
				//	}
				//

				docElem.attributes.registerSelector ( this, { match: false, constraints: constraints } );
				registeredDocElems [ docElem ] = true;
			}
			
			for ( docElemKey in registeredDocElems )
			{
				if  ( ! registeredDocElems [ docElemKey ] ) 
				{
					( docElemKey as DocumentElement ).attributes.unregisterSelector ( this );
					delete registeredDocElems [ docElemKey ];
				}
			}
		}
		
		/*
			Function: appendMatchResults
			
				Here we combine two match results into one. 
		*/
		private function appendMatchResults ( result : Object, newResult : Object ) : void
		{
			result.xmlList += newResult.xmlList;

			if ( newResult.hasOwnProperty ( "unprocessed" ) )
			{
				if ( result.hasOwnProperty ( "unprocessed" ) )
				{
					for ( var item : * in newResult.unprocessed ) result.unprocessed [ item ] = true;
				}
				else
				{
					result.unprocessed = newResult.unprocessed;
				}
			}
			
			for ( var index : * in newResult.matchedConstraints )
			{
				result.matchedConstraints [ index ] = newResult.matchedConstraints [ index ];
			}
			
			for ( index in newResult.combinedNodes )
			{
				result.combinedNodes [ index ] = newResult.combinedNodes [ index ];
			}
		}
		
		/*
			Function: matchConstraints
			
				Here we move the workspace variables xmlList and constraints
				into the matchedConstraints map.
		*/
		private function matchConstraints ( result : Object, descendantBasedConstraints : Boolean = true, reusable : Boolean = false ) : Boolean
		{
			var nodeList : * = result.xmlList;
			var newMatches : Boolean = false;
			
			if ( reusable )
			{
				if ( ! result.hasOwnProperty ( "unprocessed" ) )
				{
					result.unprocessed = new Dictionary ( true );
				}
				else
				{
					nodeList = [ ];
					for ( var item : * in result.unprocessed ) nodeList.push ( item );
				}
			}

			for each ( var node : XML in nodeList )
			{
				if ( reusable )
				{
					if ( node.@__frontalId == undefined )
					{
						// Save this node for later.
						//
						result.unprocessed [ node ] = true;
						continue;
					}
					
					delete result.unprocessed [ node ];
				}
				else if ( node.@__frontalId == undefined )
				{
					continue;
				}
				
				var frontalId : String = node.@__frontalId;
				
				// In each constraint, there should be our node or at most one 
				// node that is an ancestor of our current node.
				//
				
				var constraintsNotReady : Boolean = false;
				var matchedConstraints : Array = new Array ( );
	
				for each ( var constraint : Object in result.constraints )
				{
					// If the constraint is part of the last simple selector in
					// the selector then the constraint node is the node itelf.
					// Otherwise we find which node in the results is our 
					// ancestor.
					//
					var constraintNode : XML = node;
					if ( constraint.simpleSelectorIndex != selector.selectorPieces.length - 1 )
					{
						var constraintXMLList : XMLList = result.xmlListSubResults [ constraint.simpleSelectorIndex ];
						if ( constraintXMLList.( valueOf ( ) === node ).length ( ) == 0 ) constraintNode = constraintXMLList.( getDescendants ( valueOf ( ) ).( valueOf ( ) === node ).length ( ) > 0 ) [ 0 ];
					}
					
					var docElem : DocumentElement = document.getNodeElement ( constraintNode );
					
					// If the constraint node hasn't been parsed yet then this
					// node is not yet ready to be processed.
					//
					if ( docElem == null )
					{
						constraintsNotReady = true;
						if ( reusable ) result.unprocessed [ node ] = true;
						break;
					}
					
					matchedConstraints.push ( {
							pseudoClassName: constraint.pseudoClassName,
							pseudoClassValue: constraint.pseudoClassValue,
							negate: constraint.negate,
							match: false,
							docElem: docElem 
						} );
			
					// Relate each of the result nodes to itself if this is a
					// descendant-based combinator. We don't want to do this
					// mapping for other combinators (+ and ~) because in those
					// cases the matched nodes will not be constrained by 
					// themselves. For example, "text:hover ~ text." In this 
					// case a sibling text node is not constrined by itself but
					// by a previous sibling.
					//
					if ( descendantBasedConstraints )
					{
						if ( result.combinedNodes [ frontalId ] == null ) result.combinedNodes [ frontalId ] = { };
						result.combinedNodes [ frontalId ] [ frontalId ] = true;
					}
				}
				
				if ( ! constraintsNotReady )
				{
					if ( result.matchedConstraints [ frontalId ] == null ) result.matchedConstraints [ frontalId ] = new Array ( );
					result.matchedConstraints [ frontalId ] = result.matchedConstraints [ frontalId ].concat ( matchedConstraints );
					newMatches = true;
				}
			}
			
			if ( ! reusable ) result.constraints = [ ];
			
			return newMatches;
		}
		
		/**            
		 * @private
		 */
		internal function evaluateSelector ( result : Object, startIndex : uint = 0 ) : void
		{
			var i : uint = startIndex;
			var includeDescendants : Boolean = true;
			
			// If startIndex is not zero then we are re-using another selector's
			// search results. In that case, we need to see if there is a 
			// combinator before our starting point indicating if we are 
			// matching on descendants or not.
			//
			if ( 
				startIndex > 1 && selector.selectorPieces [ startIndex - 1 ].objectType == "Combinator" && 
				( 
					selector.selectorPieces [ startIndex - 1 ].value == ">" || 
					selector.selectorPieces [ startIndex - 1 ].value == "+" || 
					selector.selectorPieces [ startIndex - 1 ].value == "~" 
				) )
			{
				includeDescendants = false;
			}
			
			while ( i < selector.selectorPieces.length )
			{
				var simpleSelector : Object = selector.selectorPieces [ i ];
				
				if ( simpleSelector.objectType == "SimpleSelector" )
				{
					evaluateSimpleSelector ( result, simpleSelector, includeDescendants, i == 0, i );
					result.xmlListSubResults [ i ] = new XMLList ( );
					result.xmlListSubResults [ i ] += result.xmlList;
					i++;
				}
				
				if ( i < selector.selectorPieces.length )
				{
					if ( selector.selectorPieces [ i ].objectType == "Combinator" )
					{
						var combinator : Object = selector.selectorPieces [ i ];
						
						if ( combinator.value == ">" )
						{
							includeDescendants = false;
							result.xmlList = result.xmlList.children ( );
						}
						else if ( combinator.value == "+" || combinator.value == "~" )
						{
							/*
								+: Get all nodes that have their immediate 
								previous sibling in the result set.
							
								~: Get all nodes that have any previous sibling 
								in the result set.
							
								For no combinator or descendant-based
								combinators (spaces and ">") our constraints are
								winnowed by virtue of their being ancestors of
								the final matching node. With this (and other
								future) combinators, that is not true. So, we
								need to store away the current constraints along
								with info about this combinator and then create
								a new constraint list for anything we see
								further along in the selector.
							*/
							includeDescendants = false;
							matchConstraints ( result, false );
							
							var newXMLList : XMLList = new XMLList ( );
							
							for each ( var node : XML in result.xmlList )
							{
								var nextSibling : XML = node.parent ( ).children ( ) [ node.childIndex ( ) + 1 ];
								while ( nextSibling != null )
								{
									if ( nextSibling.@__frontalId != null )
									{
										var frontalId : String = nextSibling.@__frontalId;
										if ( result.combinedNodes [ frontalId ] == null ) result.combinedNodes [ frontalId ] = { };
										if ( node.@__frontalId != null ) result.combinedNodes [ frontalId ] [ node.@__frontalId ] = true;
										newXMLList += nextSibling;
									}
									
									if ( combinator.value == "+" ) break;
									
									nextSibling = node.parent ( ).children ( ) [ nextSibling.childIndex ( ) + 1 ];
								}
							}
							
							result.xmlList = newXMLList;
						}
						else
						{
							includeDescendants = true;
						}
						
						i++;
					}
					else 
					{
						includeDescendants = true;
						result.xmlList = getDescendants ( result.xmlList );
					}
				}
			}
		}
		
		/**            
		 * @private
		 */
		internal function getDescendants ( xmlList : * ) : XMLList
		{
			if ( documentDescendants [ document ] [ xmlList ] == null ) documentDescendants [ document ] [ xmlList ] = xmlList.descendants ( );
			return documentDescendants [ document ] [ xmlList ];
		}
		
		/**            
		 * @private
		 */
		internal function evaluateSimpleSelector ( result : Object, simpleSelector : Object, includeDescendants : Boolean, isFirst : Boolean, index : uint ) : void
		{
			var results : * = result.xmlList;
			
			// XXX What about a selector like this:
			//
			//	div :not(*)
			//
			// Only match div's that have no descendants. We're not handling 
			// this.
			//
			if ( simpleSelector.elementName != null ) 
			{
				if ( includeDescendants )
				{
					var tmp : XMLList = getDescendants ( result.xmlList ).(
						! simpleSelector.negate !=
						! ( 
							localName ( ) == simpleSelector.elementName && 
							( 
								( simpleSelector.onlyNoNamespace && namespace ( ) == null ) || 
								( ! simpleSelector.onlyNoNamespace && ( simpleSelector.namespace == null || namespace ( ) == simpleSelector.namespace ) ) 
							)
						)
					);
				}
				result.xmlList = result.xmlList.( 
						! simpleSelector.negate !=
						! ( 
							localName ( ) == simpleSelector.elementName && 
							( 
								( simpleSelector.onlyNoNamespace && namespace ( ) == null ) || 
								( ! simpleSelector.onlyNoNamespace && ( simpleSelector.namespace == null || namespace ( ) == simpleSelector.namespace ) ) 
							)
						)
					);
				if ( includeDescendants ) result.xmlList = XMLList ( result.xmlList ) + tmp;
				results = result.xmlList;
			}
			else if ( ! isFirst && includeDescendants )
			{
				// Universal selector. If we're the first simple selector then
				// we'll do this after we learn if we have a cache hit.
				//
				tmp = getDescendants ( result.xmlList );
				results = XMLList ( result.xmlList ) + tmp;
			}
			
			var hitDocElemCache : Boolean = false;

			for ( i = 0; i < simpleSelector.constraints.length; i++ )
			{
				var selectorName : String = simpleSelector.constraints [ i ].name;
				var cacheResults : XMLList;
				var isCacheable : Boolean = false;
				
				switch ( simpleSelector.constraints [ i ].objectType )
				{
					case "IdSelector":
						if ( ! simpleSelector.negate )
						{
							cacheResults = elementsById == null ? null : elementsById [ selectorName ];
							isCacheable = true;
						}
						break;
						
					case "ClassSelector":
						if ( ! simpleSelector.negate )
						{
							cacheResults = elementsByClass == null ? null : elementsByClass [ selectorName ];
							isCacheable = true;
						}
						break;
				}
				
				if ( isCacheable )
				{
					hitDocElemCache = true;
					if ( results == null || cacheResults == null || isFirst )
					{
						results = cacheResults;
					}
					else
					{
						// XXX Very strange this... I tried to make a
						// Dictionary of the cache results to compare 
						// results against but objects that passed === were
						// for some reason not working as equivalent keys to
						// the Dictionary. So I came up with this crazy
						// scheme.
						//
						for each ( var node : XML in cacheResults ) node.@__frontalMatch = "x";
						results = results.( attribute ( "__frontalMatch" ).length ( ) > 0 ); 
						for each ( node in cacheResults ) delete node.@__frontalMatch;
					}
				}
			}
			
			if ( isFirst && simpleSelector.elementName == null && ! hitDocElemCache && includeDescendants )
			{
				// Universal selector
				//
				tmp = getDescendants ( result.xmlList );
				results = XMLList ( result.xmlList ) + tmp;
			}
			
			result.xmlList = results == null ? new XMLList ( ) : results;
			
			for ( var i : uint = 0; i < simpleSelector.constraints.length; i++ )
			{
				selectorName = simpleSelector.constraints [ i ].name;
				
				switch ( simpleSelector.constraints [ i ].objectType )
				{
					case "IdSelector":
						if ( simpleSelector.negate ) result.xmlList = result.xmlList.( attribute ( "id" ).length ( ) == 0 || @id != selectorName );
						break;
						
					case "ClassSelector":
						var matcher : RegExp = new RegExp ( "\\b" + selectorName + "\\b" );
						if ( simpleSelector.negate ) result.xmlList = result.xmlList.( attribute ( "class" ).length ( ) == 0 || ! matcher [ "test" ] ( @[ "class" ] ) );
						break;
					
					case "PseudoClassSelector":
						switch ( selectorName )
						{
							case "hover":
							case "active":
							case "focus":
							case "link":
							case "visited":
							case "selected":
							case "disabled":
							default: // This catches our custom pseudoclasses.
								result.constraints.push ( { 
										pseudoClassName: selectorName,
										pseudoClassValue: simpleSelector.constraints [ i ].value,
										negate: simpleSelector.negate,
										simpleSelectorIndex: index
									} );
								break;
							case "lang":
								// Select all the nodes that have a lang attribute 
								// set to simpleSelector.constraints [ i ].value.
								// XXX Or that have no lang attribute but are in a
								// Document with lang set to that value.
								//
								matcher = new RegExp ( "\\b" + simpleSelector.constraints [ i ].value + "(-|\\b)" );
								if ( simpleSelector.negate )
									result.xmlList = result.xmlList.( attribute ( "lang" ).length ( ) == 0 || ! matcher [ "test" ] ( @lang ) );
								else
									result.xmlList = result.xmlList.( attribute ( "lang" ).length ( ) > 0 && matcher [ "test" ] ( @lang ) );
								break;
							case "first-child":
								Debugger.logMessage ( Debugger.ERROR, "StylesheetSelector", "pseudo-class " + selectorName + " not supported in selector " + toString ( ) );
								break;
						}
						break;
						
					case "AttributeSelector":
						matcher = null;
						switch ( simpleSelector.constraints [ i ].op )
						{
							// XXX We need to escape these values so they aren't
							// treated as RegExp's themselves.
							//
							case "=":
								matcher = new RegExp ( "^" + simpleSelector.constraints [ i ].value + "$" );
								break;
							case "~=":
								matcher = new RegExp ( "\\b" + simpleSelector.constraints [ i ].value + "\\b" );
								break;
							case "|=":
								matcher = new RegExp ( "\\b" + simpleSelector.constraints [ i ].value + "(-|\\b)" );
								break;
							case "^=":
								matcher = new RegExp ( "^" + simpleSelector.constraints [ i ].value );
								break;
							case "$=":
								matcher = new RegExp ( simpleSelector.constraints [ i ].value + "$" );
								break;
							case "*=":
								matcher = new RegExp ( simpleSelector.constraints [ i ].value );
								break;
						}
						var attr : String = simpleSelector.constraints [ i ].attribute;
						if ( simpleSelector.negate )
							result.xmlList = result.xmlList.( attribute ( attr ).length ( ) == 0 || ( matcher != null && ! matcher [ "test" ] ( @[ attr ] ) ) );
						else
							result.xmlList = result.xmlList.( attribute ( attr ).length ( ) > 0 && ( matcher == null || matcher [ "test" ] ( @[ attr ] ) ) );
						break;
						
					case "NegationPseudoClass":
						evaluateSimpleSelector ( result, simpleSelector.constraints [ i ].simpleSelector, false, false, index );
						break;
				}
			}
		}
		
		/*
			Function: runActions
			
				When defining a declaration for a selector in a stylesheet, it
				is possible to define special system interactions named
				+onMatch and +onMismatch. Then, as a selector is matched to or
				mismatched from a DocumentElement, these actions will be called.
				This method finds the actions for this selector and runs them
				in the context of the (mis)matched DocumentElement.
				
			Parameters:
			
				match	- Is this a match (true) or a mismatch?
				docElem	- The element being (mis)matched.
		*/
		/**            
		 * @private
		 */
		internal function runActions ( match : Boolean, docElem : DocumentElement ) : void
		{
			if ( actions != null )
			{
				var action : String = actions [ match ? "onMatch" : "onMismatch" ];
				if ( action != null ) ActionScriptInterpreter.evaluate ( action, docElem, null, { }, document );
			}
		}
		
		/**            
		 * @private
		 */
		override public function destroy ( ) : void
		{
			super.destroy ( );
			
			for ( var docElemKey : * in registeredDocElems )
			{
				( docElemKey as DocumentElement ).attributes.unregisterSelector ( this );
			}
			
			manager.unregisterStylesheetSelector ( this );
			removeListener ( document, FrontalEvent.INSERT_XML, newDocumentXMLHandler );
		}
		
		/**            
		 * @private
		 */
		static internal function compareSelectors ( a : Object, b : Object ) : int
		{
			return compareSelectorsByValue ( 
					a.selector.stylesheetOrder, a.selector.order, a.selector.ids, a.selector.otherAttrsAndPseudoClasses, a.selector.elementNames,
					b.selector.stylesheetOrder, b.selector.order, b.selector.ids, b.selector.otherAttrsAndPseudoClasses, b.selector.elementNames
				);
		}
		
		/*
			Function: compareSelectorsByValue
			
				A dominant selector comes first when sorting in an ascending
				fashion.
		*/
		/**            
		 * @private
		 */
		static internal function compareSelectorsByValue ( 
				aStylesheetOrder : Number, aOrder : Number, aIds : Number, aOtherAttrsAndPseudoClasses : Number, aElementNames : Number,
				bStylesheetOrder : Number, bOrder : Number, bIds : Number, bOtherAttrsAndPseudoClasses : Number, bElementNames : Number
			) : int
		{
			if ( aIds != bIds ) return aIds > bIds ? -1 : 1;
			if ( aOtherAttrsAndPseudoClasses != bOtherAttrsAndPseudoClasses ) return aOtherAttrsAndPseudoClasses > bOtherAttrsAndPseudoClasses ? -1 : 1;
			if ( aElementNames != bElementNames ) return aElementNames > bElementNames ? -1 : 1;
			if ( aStylesheetOrder != bStylesheetOrder ) return aStylesheetOrder > bStylesheetOrder ? -1 : 1;
			if ( aOrder != bOrder ) return aOrder > bOrder ? -1 : 1;
			return 0;
		}
	}
}

